#include "stdtype.h"
#include "bit.h"

/// Static Data relocations (from aaelf64.pdf)
#define R_AARCH64_ABS64 257                     // (S + A)
#define R_AARCH64_ABS32 258                     // (S + A)
#define R_AARCH64_ABS16 259                     // (S + A)
#define R_AARCH64_PREL64 260                    // (S + A)
#define R_AARCH64_PREL32 261                    // (S + A)
#define R_AARCH64_PREL16 262                    // (S + A)
#define R_AARCH64_PLT32 314                     // (S + A)

/// Static AArch64 relocations (from aaelf64.pdf)
/* Group relocations to create a 16-, 32-, 48-, or 64-bit unsigned data value or address inline */
#define R_AARCH64_MOVW_UABS_G0 263              // (S + A)
#define R_AARCH64_MOVW_UABS_G0_NC 264           // (S + A)
#define R_AARCH64_MOVW_UABS_G1 265              // (S + A)
#define R_AARCH64_MOVW_UABS_G1_NC 266           // (S + A)
#define R_AARCH64_MOVW_UABS_G2 267              // (S + A)
#define R_AARCH64_MOVW_UABS_G2_NC 268           // (S + A)
#define R_AARCH64_MOVW_UABS_G3 269              // (S + A)
/* Group relocations to create a 16, 32, 48, or 64 bit signed data or offset value inline */
#define R_AARCH64_MOVW_SABS_G0 270              // (S + A)
#define R_AARCH64_MOVW_SABS_G1 271              // (S + A)
#define R_AARCH64_MOVW_SABS_G2 272              // (S + A)
/* Relocations to generate 19, 21 and 33 bit PC-relative addresses */
#define R_AARCH64_LD_PREL_LO19 273              // S + A - P
#define R_AARCH64_ADR_PREL_LO21 274             // S + A - P
#define R_AARCH64_ADR_PREL_PG_HI21 275          // Page(S+A) - Page(P)
#define R_AARCH64_ADR_PREL_PG_HI21_NC 276       // Page(S+A) - Page(P)
#define R_AARCH64_ADD_ABS_LO12_NC 277           // S + A
#define R_AARCH64_LDST8_ABS_LO12_NC 278         // S + A
#define R_AARCH64_LDST16_ABS_LO12_NC 284        // S + A
#define R_AARCH64_LDST32_ABS_LO12_NC 285        // S + A
#define R_AARCH64_LDST64_ABS_LO12_NC 286        // S + A
#define R_AARCH64_LDST128_ABS_LO12_NC 299       // S + A
/* Relocations for control-flow instructions - all offsets are a multiple of 4 */
#define R_AARCH64_TSTBR14 279                   // S+A-P
#define R_AARCH64_CONDBR19 280                  // S+A-P
#define R_AARCH64_JUMP26 282                    // S+A-P
#define R_AARCH64_CALL26 283                    // S+A-P

/// Dynamic relocations
#define R_AARCH64_GLOB_DAT 1025                 // S + A
#define R_AARCH64_JUMP_SLOT 1026                // S + A

/* 4k page align */
#define PAGE(v) ((v) & ~0xfff)

/**
 * symaddr = S + A
 * repaddr = P
 */
mx_bool_t mx_elfloader_relx_repair (mx_uint32_t type, mx_uint64_t repaddr, mx_uint64_t symaddr)
{
    mx_bool_t ret = mx_true;
    mx_int64_t value;

    switch (type)
    {
    /* Static AArch64 relocations */
    case R_AARCH64_ADR_PREL_PG_HI21:
        /**
         * R_<CLS>_ADR_PREL_PG_HI21 = Page(S+A)-Page(P)
         * (ADRP) immhi=ADRP[23:5] immlo=ADRP[30:29]
         */
        value = PAGE(symaddr) - PAGE(repaddr);  // calcuate the offset 4k page from symbol to instruction
        FILMSKS(*((mx_uint32_pt)repaddr), RNGMSK(23, 5), GETMSKS(value, RNGMSK(32, 14)));
        FILMSKS(*((mx_uint32_pt)repaddr), RNGMSK(30, 29), GETMSKS(value, RNGMSK(32, 12)));
        break;
    case R_AARCH64_ADD_ABS_LO12_NC:
        /**
         * R_<CLS>_ADD_ABS_LO12_NC = S + A
         * (ADD) imm12=ADD[21:10]
         */
        FILMSKS(*((mx_uint32_pt)repaddr), RNGMSK(21, 10), symaddr);
        break;
    case R_AARCH64_LDST64_ABS_LO12_NC:
        /**
         * R_<CLS>_ LDST64_ABS_LO12_NC = S + A
         * STR (immediate) imm9[20:12] = symaddr[11:3]
         */
        FILMSKS(*((mx_uint32_pt)repaddr), RNGMSK(20, 12), GETMSKS(symaddr, RNGMSK(11, 3)));
        break;
    case R_AARCH64_CALL26:
        /**
         * R_<CLS>_CALL26 = S + A - P
         * (BL) imm26=BL[25:0]
         */
        value = symaddr - repaddr;
        FILMSKS(*((mx_uint32_pt)repaddr), RNGMSK(25, 0), GETMSKS(value, RNGMSK(27, 2)));
        break;

    /* Dynamic relocations */
    case R_AARCH64_GLOB_DAT:
        /**
         * R_<CLS>_GLOB_DAT = S + A
         */
        *((mx_uint64_pt)repaddr) = symaddr;
        break;
    case R_AARCH64_JUMP_SLOT:
        /**
         * R_<CLS>_JUMP_SLOT = S + A
         */
        *((mx_uint64_pt)repaddr) = symaddr;
        break;

    default:
        ret = mx_false;
        break;
    }

    return ret;
}
